from __future__ import print_function, unicode_literals

#import pyrtl
import ctypes
import subprocess
import tempfile
import shutil
from os import path
import platform
import sys
import _ctypes
import time
from .core import working_block

from .wire import Input, Output, Const, WireVector, Register
from .memory import MemBlock, RomBlock
from .pyrtlexceptions import PyrtlError, PyrtlInternalError
from .simulation import SimulationTrace, _trace_sort_key

try:
    from collections.abc import Mapping
except ImportError:
    from collections import Mapping

"""
实现Scala中的数组间操作函数
"""
def scala_map(A,B):
    result=[]
    for a in A:
        result.append(B[a])
    return result
def flatten(A):
    result=[]
    for a in A:
        for i in a:
            result.append(i)
    return result
def flatmap(A,B):
    return flatten(scala_map(A,B))
def diff(A,B):
    result=[]
    for a in A:
        if a not in B:
            result.append(a)
    return result
def distinct(A):
    result=[]
    for a in A:
        if a not in result:
            result.append(a)
    return result
def dict_sorted(D):
    """
    对字典按key进行
    在Python中，dict字典以其元素的插入顺序记录并存储；在Scala中，hashmap哈希表无论插入顺序如何，都按照key的大小排序。
    """
    return dict(sorted(D.items(), key=lambda x: x[0]))
def intersection(A,B):
    result=[]
    for a in A:
        if a in B:
            result.append(a)
    return result

class EmptyNet:
    def __init__(self):
        self.op='?'
        self.op_param=[]
        self.args=[]
        self.dests=[]

class HyperedgeDep:
    def __init__(self,name,deps,stmt):
        self.name=name
        self.deps=deps
        self.stmt=stmt

class Graph:
    def __init__(self):
        #节点的输入集合，来源（二维数组）
        self.inNeigh=[]
        #节点的输出集合，使用（二维数组）
        self.outNeigh=[]

        #初始化寄存器列表
        self.reg_list=[]
        #初始化memory列表
        self.mem_list=[]

    #扩充邻接表大小
    def growNeighsIfNeeded(self,id):
        if id>=0:
            if id>=len(self.outNeigh):
                numElemsToGrow = id-len(self.outNeigh)+1
                for i in range(numElemsToGrow):
                    self.inNeigh.append([])
                    self.outNeigh.append([])

    #添加source到dest的边
    def addEdge(self, sourceID, destID):
        maxid=sourceID
        if destID>sourceID:
            maxid=destID
        self.growNeighsIfNeeded(maxid)
        self.outNeigh[sourceID].append(destID)
        self.inNeigh[destID].append(sourceID)

    def addEdgeIfNew(self,sourceID,destID):
        if (sourceID>=len(self.outNeigh)) or (destID not in self.outNeigh[sourceID]):
            self.addEdge(sourceID,destID)

    def extPathExists(self,sourceSet,destSet):
        """
        sourceSet和destSet均为数组输入
        """
        sourcesOnFringe=[]
        for id in sourceSet:
            for neigh in self.outNeigh[id]:
                if neigh not in sourceSet:
                    sourcesOnFringe.append(id)
                    break
        sourcesOnFringe=distinct(sourcesOnFringe)
        startingExtFrontier=distinct(diff(flatmap(sourcesOnFringe,self.outNeigh),destSet))

        def traverseUntilIntersect(frontier,reached):
            """
            frontier和reached均为数组输入
            """
            if len(frontier)==0:
                return False
            else:
                nextFrontier=distinct(diff(flatmap(frontier,self.outNeigh),reached))
                for n in nextFrontier:
                    if n in destSet:
                        return True
                for n in nextFrontier:
                    reached.append(n)
                reached=distinct(reached)
                return traverseUntilIntersect(nextFrontier,reached)
        for s in startingExtFrontier:
            sourceSet.append(s)
        sourceSet=distinct(sourceSet)
        return traverseUntilIntersect(startingExtFrontier,sourceSet)

    # 判断2个节点间是否成环
    def mergeIsAcyclic(self,u,v):
        return (not self.extPathExists([u],[v])) and (not self.extPathExists([v],[u]))

    # 判断多个节点间是否成环
    def mergeIsAcyclic_set(self,ids):
        flag=True
        for source in ids:
            if self.extPathExists([source],diff(ids,[source])):
                flag=False
                break
        return flag

    def removeDuplicateEdges(self):
        for id in range(len(self.inNeigh)):
            self.inNeigh[id]=distinct(self.inNeigh[id])
            self.outNeigh[id]=distinct(self.outNeigh[id])

    def mergeNodesMutably(self,mergeDest,mergeSources):
        """
        method：实现节点间的合并
        mergeDest：一个节点ID
        mergeSources：一组节点ID
        """
        mergedID=mergeDest
        idsToRemove=mergeSources
        idsToMerge=[]
        for id in mergeSources:
            idsToMerge.append(id)
        idsToMerge.append(mergeDest)
        combinedInNeigh=diff(distinct(flatmap(idsToMerge,self.inNeigh)),idsToMerge)
        combinedOutNeigh=diff(distinct(flatmap(idsToMerge,self.outNeigh)),idsToMerge)
        for inNeighID in combinedInNeigh:
            self.outNeigh[inNeighID]=diff(self.outNeigh[inNeighID],idsToRemove)
            if mergedID not in self.outNeigh[inNeighID]:
                self.outNeigh[inNeighID].append(mergedID)
        for outNeighID in combinedOutNeigh:
            self.inNeigh[outNeighID]=diff(self.inNeigh[outNeighID],idsToRemove)
            if mergedID not in self.inNeigh[outNeighID]:
                self.inNeigh[outNeighID].append(mergedID)

        self.inNeigh[mergedID].clear()
        for id in combinedInNeigh:
            self.inNeigh[mergedID].append(id)

        self.outNeigh[mergedID].clear()
        for id in combinedOutNeigh:
            self.outNeigh[mergedID].append(id)

        for deleteID in idsToRemove:
            self.inNeigh[deleteID].clear()
            self.outNeigh[deleteID].clear()

class MergeGraph(Graph):
    def __init__(self,g,initialAssignments=[]):
        super(MergeGraph, self).__init__()

        # 节点id 索引 合并id
        self.idToMergeID = []
        # 合并id 索引 该合并组中所有id集合
        self.mergeIDToMembers = dict()
        for i in range(len(g.inNeigh)):
            self.inNeigh.append(g.inNeigh[i])
            self.outNeigh.append(g.outNeigh[i])
            self.idToMergeID.append(i)
            self.mergeIDToMembers[i] = [i]

        if len(initialAssignments):
            self.applyInitialAssignments(initialAssignments)

    def mergeGroups(self,mergeDest,mergeSources):
        """
        method:将mergeSources节点合并进mergeDest
        mergeDest:合并入的节点ID
        mergeSources：需要合并的节点ID集合
        """
        newMembers=flatten(scala_map(mergeSources, self.mergeIDToMembers))
        for id in newMembers:
            self.idToMergeID[id]=mergeDest
            self.mergeIDToMembers[mergeDest].append(id)
        for id in mergeSources:
            self.mergeIDToMembers.pop(id)
        self.mergeNodesMutably(mergeDest,mergeSources)

    def applyInitialAssignments(self,initialAssignments):
        self.idToMergeID.clear()
        self.mergeIDToMembers.clear()
        for id in initialAssignments:
            self.idToMergeID.append(id)
        asMap=dict()
        for i in range(len(initialAssignments)):
            if initialAssignments[i] not in asMap:
                asMap[initialAssignments[i]]=[i]
            else:
                asMap[initialAssignments[i]].append(i)
        asMap=dict_sorted(asMap)
        for mergeID in asMap:
            members=asMap[mergeID]
            if mergeID in members:
                self.mergeIDToMembers[mergeID]=members
                self.mergeIDToMembers=dict_sorted(self.mergeIDToMembers)
                self.mergeNodesMutably(mergeID,diff(members,[mergeID]))

    def nodeSize(self,n):
        if n in self.mergeIDToMembers:
            return len(self.mergeIDToMembers[n])
        else:
            return 0

    def iterGroups(self):
        return self.mergeIDToMembers

def findDependencesStmt(sg,s):
    """
    method:根据输入stmt，确定源操作数，目的操作数
    sg:StatementGraph
    s:输入stmt，即一个logicnet
    return:(目的操作数,源操作数,s) HyperedgeDep
    """
    if isinstance(s,EmptyNet):
        return []
    else:
        if isinstance(s,CondMux):
            condDeps=s.mux.args[0].name
            result=[]
            for i in s.tWay:
                result.append(i)
            for i in s.fWay:
                result.append(i)
            wayDeps = []
            for i in result:
                wayDeps+=findDependencesStmt(sg,i)[0].deps
            wayDeps=distinct(wayDeps)
            return [HyperedgeDep(s.name,distinct([condDeps]+wayDeps),s)]
        else:
            if s.op in "w~&|^n+-*<>=csrx":
                dest = s.dests[0].name
                if dest in sg.reg_list:
                    dest = dest + "$next"
                args_list = []
                for arg in s.args:
                    arg_ = arg.name
                    args_list.append(arg_)
                return [HyperedgeDep(dest,args_list,s)]

            #'m':memory中读取
            if s.op == 'm':
                dest = s.dests[0].name
                if dest in sg.reg_list:
                    dest = dest + "$next"

                args_list = []
                for arg in s.args:
                    arg_ = arg.name
                    args_list.append(arg_)
                args_list.append(s.op_param[1].name)
                return [HyperedgeDep(dest,args_list,s)]

            # '@':写入memory
            if s.op == '@':
                dest = s.op_param[1].name+'$'+str(s.op_param[0])
                args_list = []
                for arg in s.args:
                    arg_ = arg.name
                    args_list.append(arg_)
                return [HyperedgeDep(dest,args_list,s)]

class StatementGraph(Graph):
    def __init__(self,logicnet_list):
        super(StatementGraph, self).__init__()
        self.nameToID=dict()
        self.idToName=[]
        #存放logicNet，以数组形式存放，一个数组内一个logicNet
        self.idToStmt=[]

        self.validNodes=[]#BitSet
        self.block=logicnet_list

        def find_register(net_list):
            for net in net_list:
                if (not isinstance(net, EmptyNet)) and (not isinstance(net, CondMux)):
                    for dest in net.dests:
                        if dest._code == 'R':
                            self.reg_list.append(dest.name)
                            self.getID(dest.name)
                    for arg in net.args:
                        if arg._code == 'R':
                            self.reg_list.append(arg.name)
                            self.getID(arg.name)
                else:
                    if isinstance(net, CondMux):
                        new_net_list=net.tWay+net.fWay
                        find_register(new_net_list)
        #找出所有Register
        find_register(self.block)
        self.reg_list=distinct(self.reg_list)

        def find_memory(net_list):
            for net in net_list:
                if (not isinstance(net, EmptyNet)) and (not isinstance(net, CondMux)):
                    if net.op in "m@":
                        self.mem_list.append(net.op_param[1].name)
                        self.getID(net.op_param[1].name)
                else:
                    if isinstance(net, CondMux):
                        new_net_list=net.tWay+net.fWay
                        find_memory(new_net_list)
        # 找出所有Memory
        find_memory(self.block)
        self.mem_list=distinct(self.mem_list)

        self.buildFromBodies(self.block)

    def getID(self,vertexName):
        if vertexName in self.nameToID:
            return self.nameToID[vertexName]
        else:
            newID=len(self.nameToID)
            self.nameToID[vertexName]=newID
            self.idToName.append(vertexName)
            self.idToStmt.append(EmptyNet())
            self.growNeighsIfNeeded(newID)
            return newID

    def S_addEdge(self,sourceName,destName):
        source=self.getID(sourceName)
        dest = self.getID(destName)
        self.addEdge(source,dest)

    def S_addEdgeIfNew(self,sourceName,destName):
        source = self.getID(sourceName)
        dest = self.getID(destName)
        self.addEdgeIfNew(source,dest)

    def addStatementNode(self,resultName,depNames,logic_net=EmptyNet()):
        for depName in depNames:
            self.S_addEdge(depName,resultName)
        potentiallyNewDestID = self.getID(resultName)
        if potentiallyNewDestID >= len(self.idToStmt):
            numElemsToGrow=potentiallyNewDestID-len(self.idToStmt)+1
            for i in range(numElemsToGrow):
                self.idToStmt.append(EmptyNet())
        self.idToStmt[potentiallyNewDestID]=logic_net
        if (resultName not in self.reg_list) and (resultName not in self.mem_list):
            self.validNodes.append(potentiallyNewDestID)

    def buildFromBodies(self,bodies):
        for net in bodies:
            result = findDependencesStmt(self, net)
            for r in result:
                self.addStatementNode(r.name, r.deps, r.stmt)

    def collectValidStmts(self,ids):
        result=[]
        for id in ids:
            if id in self.validNodes:
                result.append(id)
        return scala_map(result,self.idToStmt)

    def stmtsOrdered(self):
        return self.collectValidStmts(TopologicalSort(self))

    def S_mergeIsAcyclic(self,nameA,nameB):
        idA=self.nameToID[nameA]
        idB=self.nameToID[nameB]
        return self.mergeIsAcyclic(idA,idB)

    def stateElemNames(self):
        result=[]
        for reg in self.reg_list:
            result.append(reg)
        for mem in self.mem_list:
            result.append(mem)
        return result

    def mergeStmtsMutably(self,mergeDest,mergeSources,mergeStmt):
        idsToRemove=[]
        for i in mergeSources:
            idsToRemove.append(i)
        for id in idsToRemove:
            self.idToStmt[id]=EmptyNet()
        self.mergeNodesMutably(mergeDest,mergeSources)
        self.idToStmt[mergeDest]=mergeStmt
        mergeSources.append(mergeDest)
        flag=False
        for id in mergeSources:
            if id in self.validNodes:
                flag=True
                break
        if flag==True:
            if mergeDest not in self.validNodes:
                self.validNodes.append(mergeDest)
        else:
            if mergeDest in self.validNodes:
                self.validNodes=distinct(self.validNodes)
                self.validNodes.remove(mergeDest)
        self.validNodes=diff(self.validNodes,idsToRemove)

    def extractSourceIDs(self,arg):
        """"
        根据名字获取ID
        arg:组件名，可以是常数
        return:对应ID
        """
        return scala_map([arg],self.nameToID)

def MFFC(g,excludeSet=[],size=20):
    """
    从所有的外部输出节点和状态组件的输入节点v开始，向前搜索，找到理论上满足于MFFC(v)的尽可能多的节点，将这些节点加入MFFC(v)中
    """
    Unclaimed = -1
    Excluded = -2
    mffc = []
    mffc_partition_size = []  # size of mffc(v)
    for i in range(len(g.inNeigh)):
        mffc.append(Unclaimed)
        mffc_partition_size.append(0)
    for id in excludeSet:
        mffc[id] = Excluded

    def maximizeFFCs(fringe):
        fringeAncestors = []
        result=flatmap(fringe,g.inNeigh)
        for i in result:
            if mffc[i]==Unclaimed:
                fringeAncestors.append(i)
        fringeAncestors=distinct(fringeAncestors)
        newMembers = []
        for parent in fringeAncestors:
            childrenMFFCs = distinct(scala_map(g.outNeigh[parent],mffc))
            if len(childrenMFFCs) == 1 and childrenMFFCs[0] != Unclaimed:
                mffc[parent] = childrenMFFCs[0]
                mffc_partition_size[childrenMFFCs[0]] += 1
                if mffc_partition_size[childrenMFFCs[0]] >= size:
                    break
                newMembers.append(parent)
        newMembers=distinct(newMembers)
        if len(newMembers):
            maximizeFFCs(newMembers)

    def findMFFCs():
        unvisitedSinks = []# unvisitedSinks：外部输出信号以及状态组件输入信号
        visited = []
        for id in range(len(g.inNeigh)):
            if mffc[id] == Unclaimed and len(g.outNeigh[id]) == 0:
                unvisitedSinks.append(id)
            if mffc[id] != Unclaimed:
                visited.append(id)
        #fringe：外部输入信号和状态组件输出信号的入度信号
        fringe = distinct(flatmap(visited,g.inNeigh))
        unvisitedFringe = []
        for f in fringe:
            if mffc[f] == Unclaimed:
                unvisitedFringe.append(f)
        newMFFCseeds=[]
        for u in unvisitedSinks:
            newMFFCseeds.append(u)
        for u in unvisitedFringe:
            newMFFCseeds.append(u)
        newMFFCseeds=distinct(newMFFCseeds)
        #print('MFFC:',newMFFCseeds)
        if len(newMFFCseeds) == 0:
            return mffc
        else:
            for id in newMFFCseeds:
                mffc[id] = id
                mffc_partition_size[id] += 1
            if mffc_partition_size[id] <= size:
                maximizeFFCs(newMFFCseeds)
            return findMFFCs()
    mffc = findMFFCs()
    for id in excludeSet:
        mffc[id] = id
    partition = {}
    for index, id in enumerate(mffc):
        if id in partition.keys():
            partition[id].append(index)
        else:
            partition[id] = [index]
    # print("mffc:")
    # print(partition)
    return mffc

class AcyclicPart:
    def __init__(self,g,excludeSet=[]):
        self.mg=MergeGraph(g)
        self.excludeSet =excludeSet

    #查找小分区
    def findSmallParts(self,smallPartCutoff):
        result=[]
        for id in self.mg.mergeIDToMembers:
            if (self.mg.nodeSize(id)<smallPartCutoff) and (id not in self.excludeSet):
                result.append(id)
        return result

    #执行可能的合并操作
    def perfomMergesIfPossible(self,mergesToConsider):
        mergesMade=[]
        for mergeReq in mergesToConsider:
            if len(mergeReq)>1:
                partsStillExist=True
                for i in mergeReq:
                    if i not in self.mg.mergeIDToMembers:
                        partsStillExist=False
                        break
                if partsStillExist and self.mg.mergeIsAcyclic_set(mergeReq):
                    flag=True
                    for id in mergeReq:
                        if id in self.excludeSet:
                            flag=False
                            break
                    if flag:
                        self.mg.mergeGroups(mergeReq[0],mergeReq[1:])
                        mergesMade.append(mergeReq)
        return mergesMade

    def numEdgesRemovedByMerge(self,mergeReq):
        totalInDegree=0
        totalOutDegree=0
        for id in mergeReq:
            totalInDegree+=len(self.mg.inNeigh[id])
            totalOutDegree+=len(self.mg.outNeigh[id])
        mergedInDegree=len(diff(distinct(flatmap(mergeReq,self.mg.inNeigh)),mergeReq))
        mergedOutDegree=len(diff(distinct(flatmap(mergeReq,self.mg.outNeigh)),mergeReq))
        return totalInDegree + totalOutDegree - (mergedInDegree + mergedOutDegree)

    def coarsenWithMFFCs(self,size=20):
        mffcResults=MFFC(self.mg,self.excludeSet,size)
        self.mg.applyInitialAssignments(mffcResults)

    #将单输入分区合并到其父分区
    def mergeSingleInputPartsIntoParents(self,smallPartCutoff=10):
        smallPartIDs=self.findSmallParts(smallPartCutoff)
        singleInputIDs=[]
        for id in smallPartIDs:
            if len(self.mg.inNeigh[id])==1:
                singleInputIDs.append(id)
        singleInputParents=distinct(flatmap(singleInputIDs,self.mg.inNeigh))
        baseSingleInputIDs=diff(singleInputIDs,singleInputParents)
        for childID in baseSingleInputIDs:
            parentID=self.mg.inNeigh[childID][0]
            if parentID not in self.excludeSet and self.mg.nodeSize(parentID)<smallPartCutoff:
                self.mg.mergeGroups(parentID,[childID])
        if len(baseSingleInputIDs)<len(singleInputIDs):
            self.mergeSingleInputPartsIntoParents(smallPartCutoff)

    #将小的兄弟分区合并
    def mergeSmallSiblings(self,smallPartCutoff=5):
        smallPartIDs=self.findSmallParts(smallPartCutoff)
        inputsToSiblings=[]
        for id in smallPartIDs:
            inputsCanonicalized=sorted(distinct(self.mg.inNeigh[id]))
            flag=False
            for i in inputsToSiblings:
                if i[0]==inputsCanonicalized:
                    flag=True
                    inputsToSiblings[inputsToSiblings.index(i)][1].append(id)
                    break
            if flag==False:
                inputsToSiblings.append([inputsCanonicalized,[id]])
        mergesToConsider=[]
        for i in inputsToSiblings:
            siblingIDs=i[1]
            if len(siblingIDs)>1:
                mergesToConsider.append(siblingIDs)
        mergesMade=self.perfomMergesIfPossible(mergesToConsider)
        if len(mergesMade):
            self.mergeSmallSiblings(smallPartCutoff)

    #将小分区与任意规模的兄弟分区合并
    def mergeSmallParts(self,smallPartCutoff=10,mergeThreshold=0.5):
        smallPartIDs=self.findSmallParts(smallPartCutoff)
        mergesToConsider=[]
        for id in smallPartIDs:
            numInputs=len(self.mg.inNeigh[id])
            siblings=diff(distinct(flatmap(self.mg.inNeigh[id],self.mg.outNeigh)),[id])
            legalSiblings=[]
            for sibID in siblings:
                if sibID not in self.excludeSet and self.mg.nodeSize(sibID) < smallPartCutoff*2 :
                    legalSiblings.append(sibID)
            orderConstrSibs=[]
            #le 拓扑排序上在id之前的分区
            for le in legalSiblings:
                if le < id:
                    orderConstrSibs.append(le)
            myInputSet=distinct(self.mg.inNeigh[id])
            sibsScored=[]
            for sibID in orderConstrSibs:
                count=0
                result=distinct(self.mg.inNeigh[sibID])
                for r in result:
                    if r in myInputSet:
                        count+=1
                sibsScored.append([count/numInputs,sibID])
            choices=[]
            for sib in sibsScored:
                if sib[0]>=mergeThreshold:
                    choices.append(sib)
            choicesOrdered=sorted(choices,reverse=True)
            topChoice=[]
            for ch in choicesOrdered:
                sibID=ch[1]
                if self.mg.mergeIsAcyclic(sibID,id):
                    topChoice.append(ch)
                    break
            if len(topChoice):
                mergesToConsider.append([topChoice[0][1],id])
        mergesMade=self.perfomMergesIfPossible(mergesToConsider)
        if len(mergesMade):
            self.mergeSmallParts(smallPartCutoff,mergeThreshold)

    #向下合并小分区
    def mergeSmallPartsDown(self,smallPartCutoff=10):
        smallPartIDs=self.findSmallParts(smallPartCutoff)
        mergesToConsider=[]
        for id in smallPartIDs:
            mergeableChildren=[]
            for childID in self.mg.outNeigh[id]:
                if self.mg.mergeIsAcyclic(id,childID) and (childID not in self.excludeSet):
                    mergeableChildren.append(childID)
            if len(mergeableChildren):
                orderedByEdgesRemoved=[]
                for childID in mergeableChildren:
                    orderedByEdgesRemoved.append([self.numEdgesRemovedByMerge([id,childID]),childID])
                orderedByEdgesRemoved.sort()
                topChoice=orderedByEdgesRemoved[-1][1]
                if self.mg.nodeSize(topChoice)<smallPartCutoff:
                    mergesToConsider.append([id,topChoice])
        mergesMade=self.perfomMergesIfPossible(mergesToConsider)
        if len(mergesMade):
            self.mergeSmallPartsDown(smallPartCutoff)

    def iterParts(self):
        return self.mg.iterGroups()

    def partition(self,smallPartCutoff=5):
        self.coarsenWithMFFCs()
        self.mergeSingleInputPartsIntoParents()
        self.mergeSmallSiblings(smallPartCutoff)
        self.mergeSmallParts(smallPartCutoff, 0.5)
        self.mergeSmallPartsDown(smallPartCutoff)
        self.mergeSmallParts(2*smallPartCutoff, 0.25)

def TopologicalSort(g):
    finalOrdering=[]
    inStack=[]
    finished=[]

    def visit(v):
        if v not in inStack:
            if v not in finished:
                inStack.append(v)
                for u in g.inNeigh[v]:
                    visit(u)
                finished.append(v)
                inStack.remove(v)
                finalOrdering.append(v)
    for startingID in range(len(g.inNeigh)):
        visit(startingID)
    return finalOrdering

class CondPart():
    def __init__(self,id,stmtid,alwaysActive,inputs,memberStmts,outputsToDeclare):
        self.id=id
        self.stmtid=stmtid
        self.alwaysActive=alwaysActive
        self.inputs=inputs
        self.memberStmts=memberStmts
        self.outputsToDeclare=outputsToDeclare

def convertIntoCPStmts(sg,ap,excludedIDs):
    """
    将MergeGraph转化为CondPart数据结构
    sg：Statement
    ap：AcyclicPart后的无环划分结果
    excludedIDs：外部输入信号以及状态组件的输出信号
    """
    # idToMemberStmts 索引分区内节点
    # idToProducedOutputs 索引分区内的赋值信号名
    # idToInputNames 索引分区外部输入和状态组件输出信号
    # allInputs 所有外部输入和状态组件输出信号
    # outputsToDeclare
    alreadyDeclared=distinct(sg.stateElemNames())
    idToMemberIDs=dict_sorted(ap.iterParts())
    idToMemberStmts=dict()
    for id in idToMemberIDs:
        members=idToMemberIDs[id]
        cp=sg.idToStmt[id]
        if isinstance(cp,CondPart):
            memberStmts=cp.memberStmts
        else:
            memberStmts=scala_map(members,sg.idToStmt)
        idToMemberStmts[id]=memberStmts
    idToMemberStmts = dict_sorted(idToMemberStmts)

    idToProducedOutputs = dict()
    for id in idToMemberStmts:
        result=[]
        for stmt in idToMemberStmts[id]:
            if not isinstance(stmt,EmptyNet):
                if stmt.op=='@':
                    name=stmt.op_param[1].name
                else:
                    name=stmt.dests[0].name
                result.append(name)
        idToProducedOutputs[id]=result
    idToProducedOutputs=dict_sorted(idToProducedOutputs)

    idToInputNames=dict()
    for id in idToMemberStmts:
        memberStmts=idToMemberStmts[id]
        partDepNames=[]
        for stmt in memberStmts:
            result=findDependencesStmt(sg,stmt)
            for r in result:
                for dep in r.deps:
                    partDepNames.append(dep)
        partDepNames=distinct(partDepNames)
        externalDepNames=distinct(diff(partDepNames,diff(idToProducedOutputs[id],alreadyDeclared)))
        idToInputNames[id]=externalDepNames
    idToInputNames=dict_sorted(idToInputNames)

    allInputs=[]
    for id in idToInputNames:
        Inputs=idToInputNames[id]
        for Input in Inputs:
            allInputs.append(Input)
    allInputs=distinct(allInputs)

    validParts=[]
    for id in idToMemberIDs:
        members=idToMemberIDs[id]
        for member in members:
            if member in sg.validNodes:
                validParts.append(id)
                break
    validParts=distinct(validParts)

    partsTopoSorted=[]
    partsTopoSorted1=TopologicalSort(ap.mg)
    for id in partsTopoSorted1:
        if id in validParts:
            partsTopoSorted.append(id)
    partsTopoSorted=distinct(partsTopoSorted)

    for topoOrder in range(len(partsTopoSorted)):
        id=partsTopoSorted[topoOrder]
        #print(id)
        consumedOutputs=[]
        for i in idToProducedOutputs[id]:
            if i in allInputs:
                consumedOutputs.append(i)
        namesToDeclare=diff(consumedOutputs,alreadyDeclared)
        nameToStmts=dict()
        for stmt in idToMemberStmts[id]:
            if stmt.op=='@':
                name = stmt.op_param[1].name
            else:
                name=stmt.dests[0].name
            nameToStmts[name] = stmt
        nameToStmts=dict_sorted(nameToStmts)

        # 类型收集
        outputsToDeclare = dict()
        for name in nameToStmts:
            stmt=nameToStmts[name]
            if name in namesToDeclare:
                outputsToDeclare[name]=stmt
        outputsToDeclare=dict_sorted(outputsToDeclare)

        alwaysActive=False
        if id in excludedIDs:
            alwaysActive=True
        cpStmt=CondPart(topoOrder,id,alwaysActive,idToInputNames[id],idToMemberStmts[id],outputsToDeclare)
        sg.mergeStmtsMutably(id,diff(idToMemberIDs[id],[id]),cpStmt)

class CondMux:
    def __init__(self,name,stmt,tWay,fWay):
        """
        name:语句赋值name
        stmt:mux LogicNet
        tWay:if分支语句集合
        fWay:else分支语句集合
        """
        self.name=name
        self.mux=stmt
        self.tWay=tWay
        self.fWay=fWay

def MakeCondMux(sg,keepAvail=[]):
    """
    sg:Statement
    keepAvail:节点ID集合
    """
    def findMaxSafeWay(muxID,muxCond,thisWay,otherWay):
        """
        muxID:语句赋值name
        muxCond:mux 条件name
        thisWay:mux if name
        otherWay:mux else name
        """
        dontPass=[]
        for i in sg.extractSourceIDs(muxCond):
            dontPass.append(i)
        for i in sg.extractSourceIDs(otherWay):
            dontPass.append(i)
        for i in keepAvail:
            dontPass.append(i)
        dontPass=distinct(dontPass)
        def crawlBackToFindMFFC(frontier,inMFFC):
            def allChildrenIncluded(u):
                flag=True
                for i in sg.outNeigh[u]:
                    if i not in inMFFC:
                        flag=False
                        break
                return flag
            if len(frontier):
                toInclude=[]
                for v in frontier:
                    if (v not in dontPass) and (v not in inMFFC) and (allChildrenIncluded(v)):
                        toInclude.append(v)
                toInclude=distinct(toInclude)
                nextFrontier=distinct(flatmap(toInclude,sg.inNeigh))
                expandedMFFC=[]
                for i in inMFFC:
                    expandedMFFC.append(i)
                for i in toInclude:
                    expandedMFFC.append(i)
                expandedMFFC=distinct(expandedMFFC)
                return crawlBackToFindMFFC(nextFrontier,expandedMFFC)
            else:
                return inMFFC
        sources=distinct(sg.extractSourceIDs(thisWay))
        return diff(crawlBackToFindMFFC(sources,[muxID]),[muxID])

    def makeCondMuxesTopDown(muxIDsRemaining,muxIDToWays):
        muxesWithMuxesInside=[]
        for muxID in muxIDToWays:
            tWay=muxIDToWays[muxID][0]
            fWay=muxIDToWays[muxID][1]
            result=[]
            for i in tWay:
                result.append(i)
            for i in fWay:
                result.append(i)
            for i in result:
                if i in muxIDsRemaining:
                    muxesWithMuxesInside.append(muxID)
                    break
        muxesWithMuxesInside=distinct(muxesWithMuxesInside)
        baseMuxes=diff(muxIDsRemaining,muxesWithMuxesInside)
        if len(baseMuxes):
            for muxID in baseMuxes:
                muxExpr=sg.idToStmt[muxID]
                muxStmtName=sg.idToName[muxID]
                (tWay, fWay)=muxIDToWays[muxID]
                cmStmt=CondMux(muxStmtName,muxExpr,sg.collectValidStmts(tWay),sg.collectValidStmts(fWay))
                result=[]
                for i in tWay:
                    result.append(i)
                for i in fWay:
                    result.append(i)
                sg.mergeStmtsMutably(muxID,distinct(result),cmStmt)
            makeCondMuxesTopDown(muxesWithMuxesInside,muxIDToWays)

    muxIDs=[]
    for id in range(len(sg.idToStmt)):
        if not isinstance(sg.idToStmt[id],EmptyNet):
            if sg.idToStmt[id].op=='x':
                muxIDs.append(id)
    muxIDs=distinct(muxIDs)
    muxIDToWays=dict()
    for muxID in muxIDs:
        muxExpr=sg.idToStmt[muxID]
        tWay=findMaxSafeWay(muxID,muxExpr.args[0].name,muxExpr.args[1].name,muxExpr.args[2].name)
        fWay=findMaxSafeWay(muxID,muxExpr.args[0].name,muxExpr.args[2].name,muxExpr.args[1].name)
        muxIDToWays[muxID]=(tWay,fWay)
    muxIDToWays = dict_sorted(muxIDToWays)

    nonEmptyMuxes = []
    for muxID in muxIDs:
        result=muxIDToWays[muxID]
        tWay=result[0]
        fWay=result[1]
        if len(tWay)+len(fWay)>0:
            nonEmptyMuxes.append(muxID)
    makeCondMuxesTopDown(nonEmptyMuxes,muxIDToWays)

class ESSENT():
    def __init__(self, tracer=True, register_value_map={}, memory_value_map={}, default_value=0, block=None,CP=40,optimization=3):
        self._dll = self._dir = None
        self.block = working_block(block)
        self.block.sanity_check()
        self.CP=CP
        self.optimization=optimization

        if tracer is True:
            tracer = SimulationTrace()
        self.tracer = tracer
        self._remove_untraceable()

        self.default_value = default_value
        self._regmap = {}  # Updated below
        self._memmap = memory_value_map
        self._uid_counter = 0
        self.varname = {}  # mapping from wires and memories to C variables

        for r in self.block.wirevector_subset(Register):
            rval = register_value_map.get(r, r.reset_value)
            if rval is None:
                rval = self.default_value
            self._regmap[r] = rval

        # Passing the dictionary objects themselves since they aren't updated anywhere.
        # If that's ever not the case, will need to pass in deep copies of them like done
        # for the normal Simulation so we retain the initial values that had.
        self.tracer._set_initial_values(default_value, self._regmap, self._memmap)
        self._create_dll()
        self._initialize_mems()

    def inspect(self, w):
        """Get the latest value of the wire given, if possible."""
        if isinstance(w, WireVector):
            w = w.name
        try:
            vals = self.tracer.trace[w]
        except KeyError:
            pass
        else:
            if not vals:
                raise PyrtlError('No context available. Please run a simulation step')
            return vals[-1]
        raise PyrtlError('CompiledSimulation does not support inspecting internal WireVectors')

    def step(self, inputs):
        self.run([inputs])

    def step_multiple(self, provided_inputs={}, expected_outputs={}, nsteps=None):
        if not nsteps and len(provided_inputs) == 0:
            raise PyrtlError('need to supply either input values or a number of steps to simulate')

        if len(provided_inputs) > 0:
            longest = sorted(list(provided_inputs.items()),
                             key=lambda t: len(t[1]),
                             reverse=True)[0]
            msteps = len(longest[1])
            if nsteps:
                if (nsteps > msteps):
                    raise PyrtlError('nsteps is specified but is greater than the '
                                     'number of values supplied for each input')
            else:
                nsteps = msteps

        if nsteps < 1:
            raise PyrtlError("must simulate at least one step")

        if list(filter(lambda l: len(l) < nsteps, provided_inputs.values())):
            raise PyrtlError(
                "must supply a value for each provided wire "
                "for each step of simulation")

        if list(filter(lambda l: len(l) < nsteps, expected_outputs.values())):
            raise PyrtlError(
                "any expected outputs must have a supplied value "
                "each step of simulation")

        ordered_provided_inputs={}
        for name in self.input_order:
            name=name.name
            ordered_provided_inputs[name]=provided_inputs[name]

        len_step = 0
        for i in ordered_provided_inputs:
            len_step = len(ordered_provided_inputs[i])
            break
        inbuf = []
        for index in range(len_step):
            # inn:一次cycle的输入情况，dict形式
            inn = dict()
            for key in ordered_provided_inputs:
                inn[key] = ordered_provided_inputs[key][index]
            inbuf.append(inn)
        return self.run(inbuf)

    def run(self, inputs):
        """ Run many steps of the simulation.

        :param inputs: A list of input mappings for each step;
            its length is the number of steps to be executed.
        """
        steps = len(inputs)

        # create i/o arrays of the appropriate length
        ibuf_type = ctypes.c_uint64 * (steps * self._ibufsz)
        obuf_type = ctypes.c_uint64 * (steps * self._obufsz)
        ibuf = ibuf_type()
        obuf = obuf_type()
        # these array will be passed to _crun
        self._crun.argtypes = [ctypes.c_uint64, ibuf_type, obuf_type]

        # build the input array
        for n, inmap in enumerate(inputs):
            for w in inmap:
                if isinstance(w, WireVector):
                    name = w.name
                else:
                    name = w
                start, count = self._inputpos[name]
                start += n * self._ibufsz
                val = inmap[w]
                if val >= 1 << self._inputbw[name]:
                    raise PyrtlError(
                        'Wire {} has value {} which cannot be represented '
                        'using its bitwidth'.format(name, val))
                # pack input
                for pos in range(start, start + count):
                    ibuf[pos] = val & ((1 << 64) - 1)
                    val >>= 64
        time1 = time.perf_counter()
        self._crun(steps, ibuf, obuf)
        time2 = time.perf_counter()

        # save traced wires
        for name in self.tracer.trace:
            rname = self._probe_mapping.get(name, name)
            if rname in self._outputpos:
                start, count = self._outputpos[rname]
                buf, sz = obuf, self._obufsz
            elif rname in self._inputpos:
                start, count = self._inputpos[rname]
                buf, sz = ibuf, self._ibufsz
            else:
                raise PyrtlInternalError('Untraceable wire in tracer')
            res = []
            for n in range(steps):
                val = 0
                # unpack output
                for pos in reversed(range(start, start + count)):
                    val <<= 64
                    val |= buf[pos]
                res.append(val)
                start += sz
            self.tracer.trace[name].extend(res)

        return time2-time1

    def _traceable(self, wv):
        """ Check if wv is able to be traced.

        If it is traceable due to a probe, record that probe in _probe_mapping.
        """
        if isinstance(wv, (Input, Output)):
            return True
        for net in self.block.logic:
            if net.op == 'w' and net.args[0].name == wv.name and isinstance(net.dests[0], Output):
                self._probe_mapping[wv.name] = net.dests[0].name
                return True
        return False

    def _remove_untraceable(self):
        """ Remove from the tracer those wires that CompiledSimulation cannot track.

        Create _probe_mapping for wires only traceable via probes.
        """
        self._probe_mapping = {}
        wvs = {wv for wv in self.tracer.wires_to_track if self._traceable(wv)}
        self.tracer.wires_to_track = wvs
        self.tracer._wires = {wv.name: wv for wv in wvs}
        self.tracer.trace.__init__(wvs)

    def _create_dll(self):
        """ Create a dynamically-linked library implementing the simulation logic. """
        self._dir = tempfile.mkdtemp()
        with open(path.join(self._dir, 'pyrtlsim_essent.c'), 'w') as f:
            self._create_code(lambda s: f.write(s + '\n'))
        if platform.system() == 'Darwin':
            shared = '-dynamiclib'
        else:
            shared = '-shared'
        subprocess.check_call(['gcc', '-O3', '-march=native', '-std=c99', '-m64',
                               shared, '-fPIC',
                               path.join(self._dir, 'pyrtlsim_essent.c'),
                               '-o', path.join(self._dir, 'pyrtlsim_essent.so'), ],
                              shell=(platform.system() == 'Windows'))
        self._dll = ctypes.CDLL(path.join(self._dir, 'pyrtlsim_essent.so'))
        self._crun = self._dll.sim_run_all
        self._crun.restype = None  # argtypes set on use
        self._initialize_mems = self._dll.initialize_mems
        self._initialize_mems.restype = None
        self._mem_lookup = self._dll.lookup
        self._mem_lookup.restype = ctypes.POINTER(ctypes.c_uint64)

    def _limbs(self, w):
        """ Number of 64-bit words needed to store value of wire. """
        return (w.bitwidth + 63) // 64

    def _makeini(self, w, v):
        """ C initializer string for a wire with a given value. """
        pieces = []
        for _ in range(self._limbs(w)):
            pieces.append(hex(v & ((1 << 64) - 1)))
            v >>= 64
        return ','.join(pieces).join('{}')

    def _romwidth(self, m):
        """ Bitwidth of integer type sufficient to hold rom entry.

        On large memories, returns 64; an array will be needed.
        """
        if m.bitwidth <= 8:
            return 8
        if m.bitwidth <= 16:
            return 16
        if m.bitwidth <= 32:
            return 32
        return 64

    def _makemask(self, dest, res, pos):
        """Create a bitmask.

        The value being masked is of width `res`.
        Limb number `pos` of `dest` is being assigned to.
        """
        if (res is None or dest.bitwidth < res) and 0 < (dest.bitwidth - 64 * pos) < 64:
            return '&0x{:X}'.format((1 << (dest.bitwidth % 64)) - 1)
        return ''

    def _getarglimb(self, arg, n):
        """ Get the nth limb of the given wire.

        Returns '0' when the wire does not have sufficient limbs.
        """
        return '{vn}[{n}]'.format(vn=self.varname[arg], n=n) if arg.bitwidth > 64 * n else '0'

    def _clean_name(self, prefix, obj):
        """ Create a C variable name with the given prefix based on the name of obj. """
        return '{}{}_{}'.format(prefix, self._uid(), ''.join(c for c in obj.name if c.isalnum()))

    def _uid(self):
        """ Get an auto-incrementing number suitable for use as a unique identifier. """
        x = self._uid_counter
        self._uid_counter += 1
        return x

    def _declare_roms(self, write, roms):
        for mem in roms:
            self.varname[mem] = vn = self._clean_name('m', mem)
            # extract data from mem
            romval = [mem._get_read_data(n) for n in range(1 << mem.addrwidth)]
            write('static const uint{width}_t {name}[][{limbs}] = {{'.format(
                name=vn, width=self._romwidth(mem), limbs=self._limbs(mem)))
            for rv in romval:
                write(self._makeini(mem, rv) + ',')
            write('};')

    def _declare_mems(self, write, mems):
        for mem in mems:
            self.varname[mem] = vn = self._clean_name('m', mem)
            write('EXPORT')
            write('hashmap_t *{name};'.format(name=vn))

        next_tmp = 0
        write('EXPORT')
        write('void initialize_mems() {')
        for mem in mems:
            # Create hashmap
            write('{name} = create_hash_map(256, {limbs});'.format(
                name=self.varname[mem], limbs=self._limbs(mem)
            ))
            if mem in self._memmap:
                # Insert default values
                for k, v in self._memmap[mem].items():
                    write('val_t t{n}[] = {val};'.format(
                        n=next_tmp, val=self._makeini(mem, v)))
                    write('insert({name}, {key}, t{n});'.format(
                        name=self.varname[mem], key=k, n=next_tmp
                    ))
                    next_tmp += 1
        write('}\n')
    def _declare_wire(self, write, w, type):
        self.varname[w] = ESSENT._clean_name(self, 'w', w)
        vn = self.varname[w]
        if isinstance(w, Const):
            pieces = []
            for _ in range(self._limbs(w)):
                pieces.append(hex(w.val & ((1 << 64) - 1)))
                w.val >>= 64
            write('{type} {name}[{limbs}];'.format(
                type=type,limbs=self._limbs(w), name=vn, val=self._makeini(w, w.val)))
            for limb in range(self._limbs(w)):
                write('{name}[{limb}]={val};'.format(name=vn, limb=limb, val=pieces[limb]))
        elif isinstance(w, Register):
            rval = self._regmap.get(w, w.reset_value)
            if rval is None:
                rval = self.default_value

            write('{type} {name}[{limbs}]={val};'.format(type=type,
                limbs=self._limbs(w), name=vn, val=self._makeini(w, rval)))

            write('{type} {name}$new[{limbs}];'.format(type=type,
                limbs=self._limbs(w), name=vn, val=self._makeini(w, rval)))

        else:
            if isinstance(w,Input):
                write('{type} {vn}[{n}];'.format(type=type,vn=self.varname[w], n=self._limbs(w)))
                write('{type} {vn}$old[{n}];'.format(type=type,vn=self.varname[w],n=self._limbs(w)))

            elif w.name in self.outputsToDeclare:
                write('{type} {vn}[{n}];'.format(type=type,vn=self.varname[w], n=self._limbs(w)))
                write('{type} {vn}$old[{n}];'.format(type=type,vn=self.varname[w], n=self._limbs(w)))
            else:
                write('{type} {vn}[{n}];'.format(type=type,vn=self.varname[w], n=self._limbs(w)))

    # def _declare_wire(self, write, w, type):
    #     self.varname[w] = vn = self._clean_name('w', w)
    #     if isinstance(w, Const):
    #         pieces = []
    #         for _ in range(self._limbs(w)):
    #             pieces.append(hex(w.val & ((1 << 64) - 1)))
    #             w.val >>= 64
    #         write('__shared__ {type} {name}[{limbs}];'.format(
    #             type=type,limbs=self._limbs(w), name=vn, val=self._makeini(w, w.val)))
    #         for limb in range(self._limbs(w)):
    #             write('{name}[{limb}]={val};'.format(name=vn, limb=limb, val=pieces[limb]))
    #     elif isinstance(w, Register):
    #         rval = self._regmap.get(w, w.reset_value)
    #         if rval is None:
    #             rval = self.default_value
    #
    #         write('__shared__ {type} {name}[{limbs}];'.format(type=type,
    #             limbs=self._limbs(w), name=vn, val=self._makeini(w, rval)))
    #
    #         write('__shared__ {type} {name}$new[{limbs}];'.format(type=type,
    #             limbs=self._limbs(w), name=vn, val=self._makeini(w, rval)))
    #
    #     else:
    #         if isinstance(w,Input):
    #             write('__shared__ {type} {vn}[{n}];'.format(type=type,vn=self.varname[w], n=self._limbs(w)))
    #             write('__shared__ {type} {vn}$old[{n}];'.format(type=type,vn=self.varname[w],n=self._limbs(w)))
    #
    #         elif w.name in self.outputsToDeclare:
    #             write('__shared__ {type} {vn}[{n}];'.format(type=type,vn=self.varname[w], n=self._limbs(w)))
    #             write('__shared__ {type} {vn}$old[{n}];'.format(type=type,vn=self.varname[w], n=self._limbs(w)))
    #         else:
    #             write('__shared__ {type} {vn}[{n}];'.format(type=type,vn=self.varname[w], n=self._limbs(w)))

    def _build_memory_write(self, write, op, op_param, args, dest, level):
        mem = op_param[1]
        # write('// net {op} : {args} -> {dest}'.format(
        #     op=op, args=', '.join(self.varname[x] for x in args), dest=self.varname[mem]))
        write(level * '  ' + 'if ({enable}[0]) {{'.format(enable=self.varname[args[2]]))
        write((1 + level) * '  ' + 'insert({mem},{addr}[j] ,{vn},i*W+j);'.format(
            mem=self.varname[mem],
            addr=self.varname[args[0]],
            vn=self.varname[args[1]]
        ))
        write(level * '  ' + '}')

    def _build_register_update(self, write, op, op_param, args, dest, level):
        for n in range(self._limbs(dest)):
            write(level * '  ' + '{reg}$new[{n}] = {vn}[{n}];'.format(vn=self.varname[args[0]], n=n,reg=self.varname[dest]))

    def _build_memread(self, write, op, param, args, dest, level):
        mem = param[1]
        for n in range(self._limbs(dest)):
            if isinstance(mem, RomBlock):
                write(level * '  ' + '{dest}[{n}] = *({mem}+{addr}[j]*{limb}+{n}){mask};'.format(
                    dest=self.varname[dest], n=n, mem=self.varname[mem],limb=self._limbs(mem),
                    addr=self.varname[args[0]], mask=self._makemask(dest, mem.bitwidth, n)))
            else:
                write(level * '  ' + '{dest}[{n}] = lookup({mem}, *({addr}+j),i*W+j)[{n}]{mask};'.format(
                    dest=self.varname[dest], n=n, mem=self.varname[mem],
                    addr=self.varname[args[0]], mask=self._makemask(dest, mem.bitwidth, n)))

    def _build_wire(self, write, op, param, args, dest, level):
        for n in range(self._limbs(dest)):
            write(level * '  ' + '{dest}[{n}] = {arg}[{n}]{mask};'.format(
                dest=self.varname[dest], n=n, arg=self.varname[args[0]],
                mask=self._makemask(dest, args[0].bitwidth, n)))

    def _build_not(self, write, op, param, args, dest, level):
        for n in range(self._limbs(dest)):
            write(level * '  ' + '{dest}[{n}] = (~{arg}[{n}]){mask};'.format(
                dest=self.varname[dest], n=n, arg=self.varname[args[0]],
                mask=self._makemask(dest, None, n)))

    def _build_bitwise(self, write, op, param, args, dest, level):  # &, |, ^ only
        for n in range(self._limbs(dest)):
            arg0 = self._getarglimb(args[0], n)
            arg1 = self._getarglimb(args[1], n)
            write(level * '  ' + '{dest}[{n}] = ({arg0}{op}{arg1}){mask};'.format(
                dest=self.varname[dest], n=n, arg0=arg0, arg1=arg1, op=op,
                mask=self._makemask(dest, max(args[0].bitwidth, args[1].bitwidth), n)))

    def _build_nand(self, write, op, param, args, dest, level):
        for n in range(self._limbs(dest)):
            arg0 = self._getarglimb(args[0], n)
            arg1 = self._getarglimb(args[1], n)
            write(level * '  ' + '{dest}[{n}] = (~({arg0}&{arg1})){mask};'.format(
                dest=self.varname[dest], n=n, arg0=arg0, arg1=arg1,
                mask=self._makemask(dest, None, n)))

    def _build_eq(self, write, op, param, args, dest, level):
        cond = []
        for n in range(max(self._limbs(args[0]), self._limbs(args[1]))):
            arg0 = self._getarglimb(args[0], n)
            arg1 = self._getarglimb(args[1], n)
            cond.append('({arg0}=={arg1})'.format(arg0=arg0, arg1=arg1))
        write(level * '  ' + '{dest}[0] = {cond};'.format(dest=self.varname[dest], cond='&&'.join(cond)))

    def _build_cmp(self, write, op, param, args, dest, level):  # <, > only
        cond = None
        for n in range(max(self._limbs(args[0]), self._limbs(args[1]))):
            arg0 = self._getarglimb(args[0], n)
            arg1 = self._getarglimb(args[1], n)
            c = '({arg0}{op}{arg1})'.format(arg0=arg0, op=op, arg1=arg1)
            if cond is None:
                cond = c
            else:
                cond = '({c}||(({arg0}=={arg1})&&{inner}))'.format(
                    c=c, arg0=arg0, arg1=arg1, inner=cond)
        write(level * '  ' + '{dest}[0] = {cond};'.format(dest=self.varname[dest], cond=cond))

    def _build_mux(self, write, op, param, args, dest, level):
        write(level * '  ' + 'if ({mux}[0])'.format(mux=self.varname[args[0]]))
        write(level * '  ' + '{')
        for n in range(self._limbs(dest)):
            write((1 + level) * '  ' + '{dest}[{n}] = {arg}[{n}]{mask};'.format(
                dest=self.varname[dest], n=n, arg=self.varname[args[2]],
                mask=self._makemask(dest, args[2].bitwidth, n)))
        write(level * '  ' + '}')
        write(level * '  ' + 'else')
        write(level * '  ' + '{')
        for n in range(self._limbs(dest)):
            write((1 + level) * '  ' + '{dest}[{n}] = {arg}[{n}]{mask};'.format(
                dest=self.varname[dest], n=n, arg=self.varname[args[1]],
                mask=self._makemask(dest, args[1].bitwidth, n)))
        write(level * '  ' + '}')

    def _build_add(self, write, op, param, args, dest, level):
        write(level * '  ' + 'carry = 0;')
        for n in range(self._limbs(dest)):
            arg0 = self._getarglimb(args[0], n)
            arg1 = self._getarglimb(args[1], n)
            write(level * '  ' + 'tmp = {arg0}+{arg1};'.format(arg0=arg0, arg1=arg1))
            write(level * '  ' + '{dest}[{n}] = (tmp + carry){mask};'.format(
                dest=self.varname[dest], n=n,
                mask=self._makemask(dest, max(args[0].bitwidth, args[1].bitwidth) + 1, n)))
            write(level * '  ' + 'carry = (tmp < {arg0})|({dest}[{n}] < tmp);'.format(
                arg0=arg0, dest=self.varname[dest], n=n))

    def _build_sub(self, write, op, param, args, dest, level):
        write(level * '  ' + 'carry = 0;')
        for n in range(self._limbs(dest)):
            arg0 = self._getarglimb(args[0], n)
            arg1 = self._getarglimb(args[1], n)
            write(level * '  ' + 'tmp = {arg0}-{arg1};'.format(arg0=arg0, arg1=arg1))
            write(level * '  ' + '{dest}[{n}] = (tmp - carry){mask};'.format(
                dest=self.varname[dest], n=n, mask=self._makemask(dest, None, n)))
            write(level * '  ' + 'carry = (tmp > {arg0})|({dest}[{n}] > tmp);'.format(
                arg0=arg0, dest=self.varname[dest], n=n))

    def _build_mul(self, write, op, param, args, dest, level):
        for n in range(self._limbs(dest)):
            write(level * '  ' + '{dest}[{n}] = 0;'.format(dest=self.varname[dest], n=n))
        for p0 in range(self._limbs(args[0])):
            write(level * '  ' + 'carry = 0;')
            arg0 = self._getarglimb(args[0], p0)
            for p1 in range(self._limbs(args[1])):
                if self._limbs(dest) <= p0 + p1:
                    break
                arg1 = self._getarglimb(args[1], p1)
                write(level * '  ' + 'gpu_mul128({arg0}, {arg1}, &tmplo, &tmphi);'.format(arg0=arg0, arg1=arg1))
                write(level * '  ' + 'tmp = {dest}[{p}];'.format(dest=self.varname[dest], p=p0 + p1))
                write(level * '  ' + 'tmplo += carry; carry = tmplo < carry; tmplo += tmp;')
                write(level * '  ' + 'tmphi += carry + (tmplo < tmp); carry = tmphi;')
                write(level * '  ' + '{dest}[{p}] = tmplo{mask};'.format(
                    dest=self.varname[dest], p=p0 + p1,
                    mask=self._makemask(dest, args[0].bitwidth + args[1].bitwidth, p0 + p1)))
            if self._limbs(dest) > p0 + self._limbs(args[1]):
                write(level * '  ' + '{dest}[{p}] = carry{mask};'.format(
                    dest=self.varname[dest], p=p0 + self._limbs(args[1]),
                    mask=self._makemask(
                        dest, args[0].bitwidth + args[1].bitwidth, p0 + self._limbs(args[1]))))

    def _build_concat(self, write, op, param, args, dest, level):
        cattotal = sum(x.bitwidth for x in args)
        pieces = (
            (self.varname[a], lx, 0, min(64, a.bitwidth - 64 * lx))
            for a in reversed(args) for lx in range(self._limbs(a)))
        curr = next(pieces)
        for n in range(self._limbs(dest)):
            res = []
            dpos = 0
            while True:
                arg, alimb, astart, asize = curr
                res.append('(((uint64_t)({arg}[{limb}]>>{start}))<<{pos})'.format(
                    arg=arg, limb=alimb, start=astart, pos=dpos))
                dpos += asize
                if dpos >= dest.bitwidth - 64 * n:
                    break
                if dpos > 64:
                    curr = (arg, alimb, 64 - (dpos - asize), dpos - 64)
                    break
                curr = next(pieces)
                if dpos == 64:
                    break
            write(level * '  ' + '{dest}[{n}] = ({res}){mask};'.format(
                dest=self.varname[dest], n=n, res='|'.join(res),
                mask=self._makemask(dest, cattotal, n)))

    def _build_select(self, write, op, param, args, dest, level):
        for n in range(self._limbs(dest)):
            bits = [
                '((1&((uint64_t)({src}[{limb}]>>{sb})))<<{db})'.format(
                    src=self.varname[args[0]], sb=(b % 64), limb=(b // 64), db=en)
                for en, b in enumerate(param[64 * n:min(dest.bitwidth, 64 * (n + 1))])]
            write(level * '  ' + '{dest}[{n}] = {bits};'.format(
                dest=self.varname[dest], n=n, bits='|'.join(bits)))

    def _declare_mem_helpers(self, write):
        helpers = '''
typedef uint64_t val_t;

typedef struct node
{
    uint64_t key;
    val_t *val;
    struct node *next;
} node_t;

typedef struct hashmap
{
    int size;
    int val_limbs;
    val_t *default_value;
    node_t **list;
} hashmap_t;

hashmap_t *create_hash_map(int size, int val_limbs)
{
    int i;
    hashmap_t *h = (hashmap_t *) malloc(sizeof(hashmap_t));
    h->size = size;
    h->val_limbs = val_limbs;
    h->list = (node_t **) malloc(sizeof(node_t *) * size);
    h->default_value = (val_t *) malloc(sizeof(val_t) * val_limbs);
    for (i = 0; i < val_limbs; i++)
        h->default_value[i] = 0;
    for (i = 0; i < size; i++)
        h->list[i] = NULL;
    return h;
}

int hash_code(hashmap_t *h, uint64_t key)
{
    return key % h->size;
}

void insert(hashmap_t *h, uint64_t key, val_t val[])
{
    int pos = hash_code(h, key);
    struct node *list = h->list[pos];
    struct node *new_node = (node_t *) malloc(sizeof(node_t));
    struct node *temp = list;
    while (temp)
    {
        if (temp->key == key)
        {
            memcpy(temp->val, val, sizeof(val_t) * h->val_limbs);
            return;
        }
        temp = temp->next;
    }
    new_node->key = key;
    new_node->val = (val_t *) malloc(sizeof(val_t) * h->val_limbs);
    memcpy(new_node->val, val, sizeof(val_t) * h->val_limbs);
    new_node->next = list;
    h->list[pos] = new_node;
}

EXPORT
val_t* lookup(hashmap_t *h, uint64_t key)
{
    int pos = hash_code(h, key);
    node_t *list = h->list[pos];
    node_t *temp = list;
    while (temp)
    {
        if (temp->key == key)
        {
            return temp->val;
        }
        temp = temp->next;
    }
    return h->default_value;
}
        '''
        write(helpers)

    def writeBodyInner(self,sg,flag,write,level,id,keepAvail=[]):
        op_builders = {
            'm': self._build_memread,
            'w': self._build_wire,
            '~': self._build_not,
            '&': self._build_bitwise,
            '|': self._build_bitwise,
            '^': self._build_bitwise,
            'n': self._build_nand,
            '=': self._build_eq,
            '<': self._build_cmp,
            '>': self._build_cmp,
            '+': self._build_add,
            '-': self._build_sub,
            '*': self._build_mul,
            'c': self._build_concat,
            's': self._build_select,
            'x': self._build_mux,
        }
        if flag:
            MakeCondMux(sg,keepAvail)
        for stmt in sg.stmtsOrdered():
            if isinstance(stmt,CondMux):
                if self.optimization in [1, 3]:
                    mux = stmt.mux
                    op, param, args, dest = mux.op, mux.op_param, mux.args, mux.dests[0]
                    write(level*'  '+'if ({mux}[0])'.format(mux=self.varname[args[0]]))
                    write(level*'  '+'{')
                    self.writeBodyInner(StatementGraph(stmt.fWay), False,write,level+1,id,keepAvail)
                    for n in range(self._limbs(dest)):
                        write((1+level)*'  '+'{dest}[{n}] = {arg}[{n}]{mask};'.format(
                            dest=self.varname[dest], n=n, arg=self.varname[args[2]],
                            mask=self._makemask(dest, args[2].bitwidth, n)))
                    write(level*'  '+'}')
                    write(level*'  '+'else')
                    write(level * '  '+'{')
                    self.writeBodyInner(StatementGraph(stmt.tWay), False,write,level+1,id,keepAvail)
                    for n in range(self._limbs(dest)):
                        write((1+level)*'  '+'{dest}[{n}] = {arg}[{n}]{mask};'.format(
                            dest=self.varname[dest], n=n, arg=self.varname[args[1]],
                            mask=self._makemask(dest, args[1].bitwidth, n)))
                    write(level*'  '+'}')

                else:
                    mux = stmt.mux
                    op, param, args, dest = mux.op, mux.op_param, mux.args, mux.dests[0]
                    self.writeBodyInner(StatementGraph(stmt.fWay), False, write, level, id, keepAvail)
                    self.writeBodyInner(StatementGraph(stmt.tWay), False, write, level, id, keepAvail)
                    write(level * '  ' + 'if ({mux}[0])'.format(mux=self.varname[args[0]]))
                    write(level * '  ' + '{')
                    for n in range(self._limbs(dest)):
                        write((1 + level) * '  ' + '{dest}[{n}] = {arg}[{n}]{mask};'.format(
                            dest=self.varname[dest], n=n, arg=self.varname[args[2]],
                            mask=self._makemask(dest, args[2].bitwidth, n)))
                    write(level * '  ' + '}')
                    write(level * '  ' + 'else')
                    write(level * '  ' + '{')
                    for n in range(self._limbs(dest)):
                        write((1+level)*'  '+'{dest}[{n}] = {arg}[{n}]{mask};'.format(
                            dest=self.varname[dest], n=n, arg=self.varname[args[1]],
                            mask=self._makemask(dest, args[1].bitwidth, n)))
                    write(level*'  '+'}')

            else:
                if stmt.op == 'r':
                    op, param, args, dest = stmt.op, stmt.op_param, stmt.args, stmt.dests[0]
                    self._build_register_update(write, op, param, args, dest, level)

                elif stmt.op=='@':
                    self.memory_update.append(stmt)

                else:
                    op, param, args, dest = stmt.op, stmt.op_param, stmt.args, stmt.dests[0]
                    # write('// net {op} : {args} -> {dest}'.format(
                    #     op=op, args=', '.join(self.varname[x] for x in args), dest=self.varname[dest]))
                    op_builders[op](write, op, param, args, dest,level)


    def _create_code(self,write):
        # ESSENT初步处理转换，划分
        sg = StatementGraph(self.block)
        excludedIDs = []
        for i in range(len(sg.inNeigh)):
            if i not in sg.validNodes:
                excludedIDs.append(i)
        ap = AcyclicPart(sg, distinct(excludedIDs))
        ap.partition(self.CP)
        convertIntoCPStmts(sg, ap, excludedIDs)
        stmtsOrdered = sg.stmtsOrdered()
        self.stmtsOrdered=len(stmtsOrdered)
        # print(len(stmtsOrdered))

        # 记录每个寄存器的使用者，用于寄存器更新后修改Flag
        self.register_dependence = dict()
        # 记录每个memory的使用者，用于memory更新后修改Flag
        self.memory_dependence = dict()
        # 记录每个outputsToDeclare的使用者，用于outputsToDeclare被赋值后修改Flag
        self.outputsToDeclare_dependence = dict()
        self.outputsToDeclare = []
        self.outputsToDeclare_information = dict()
        for cp in stmtsOrdered:
            id = cp.id
            for logicnet in cp.memberStmts:
                for arg in logicnet.args:
                    if isinstance(arg, Register):
                        reg=arg.name
                        if reg not in self.register_dependence:
                            self.register_dependence[reg] = [id]
                        else:
                            if id not in self.register_dependence[reg]:
                                self.register_dependence[reg].append(id)
                if logicnet.op == 'm':
                    mem=logicnet.op_param[1].name
                    if mem not in self.memory_dependence:
                        self.memory_dependence[mem] = [id]
                    else:
                        if id not in self.memory_dependence[mem]:
                            self.memory_dependence[mem].append(id)
            for out in cp.outputsToDeclare:
                self.outputsToDeclare_dependence[out] = []
                self.outputsToDeclare.append(out)
        for cp in stmtsOrdered:
            id = cp.id
            for logicnet in cp.memberStmts:
                for arg in logicnet.args:
                    if arg.name in self.outputsToDeclare and arg.name not in cp.outputsToDeclare:
                        self.outputsToDeclare_information[arg.name] = arg
                        if id not in self.outputsToDeclare_dependence[arg.name]:
                            self.outputsToDeclare_dependence[arg.name].append(id)

        write('#include <stdint.h>')
        write('#include <stdlib.h>')
        write('#include <string.h>')
        write('#include <stdio.h>')
        if platform.system() == 'Windows':
            write('#define EXPORT __declspec(dllexport)')
        else:
            write('#define EXPORT')
        machine_alias = {'amd64': 'x86_64', 'aarch64': 'arm64', 'aarch64_be': 'arm64'}
        machine = platform.machine().lower()
        machine = machine_alias.get(machine, machine)
        mulinstr = {
            'x86_64': '"mulq %q3":"=a"(pl),"=d"(ph):"%0"(t0),"r"(t1):"cc"',
            'arm64': '"mul %0, %2, %3\n\tumulh %1, %2, %3":'
                     '"=&r"(*pl),"=r"(*ph):"r"(t0),"r"(t1):"cc"',
            'mips64': '"dmultu %2, %3\n\tmflo %0\n\tmfhi %1":'
                      '"=r"(*pl),"=r"(*ph):"r"(t0),"r"(t1)',
        }
        if machine in mulinstr:
            write('#define mul128(t0, t1, pl, ph) __asm__({})'.format(mulinstr[machine]))

        #声明定义memory
        mems = {net.op_param[1] for net in self.block.logic_subset('m@')}
        for key in self._memmap:
            if key not in mems:
                raise PyrtlError('unrecognized MemBlock in memory_value_map')
            if isinstance(key, RomBlock):
                raise PyrtlError('RomBlock in memory_value_map')
        self._declare_mem_helpers(write)
        roms = {mem for mem in mems if isinstance(mem, RomBlock)}
        self._declare_roms(write, roms)
        mems = {mem for mem in mems if isinstance(mem, MemBlock) and not isinstance(mem, RomBlock)}
        self._declare_mems(write, mems)

        #书写总激励函数
        write('EXPORT')
        write('void sim_run_all(uint64_t stepcount, uint64_t inputs[], uint64_t outputs[])')
        write('{')

        #定义常量，寄存器，跨区间信号，中间信号
        for w in self.block.wirevector_set:
            self._declare_wire(write, w, 1)
        write('  ' + 'uint64_t tmp, carry, tmphi, tmplo;')
        write('')

        # 声明定义Flag
        if self.optimization in [2,3]:
            write('  ' + 'int Flag_same_inputs[{n}]='.format(n=len(stmtsOrdered)) + '{' + '0,' * len(stmtsOrdered) + '};')
            write(' ')

        # 纠正输入顺序，声明定义所有输入信号以及old值
        self.input_order = []
        self._inputpos = {}
        self._inputbw = {}
        ipos = 0
        inputs = []
        for input_ in self.block.wirevector_subset(Input):
            inputs.append(input_.name)
        inputs.sort()
        for i in inputs:
            for input_ in self.block.wirevector_subset(Input):
                if input_.name == i:
                    self.input_order.append(input_)
                    break
        for w in self.input_order:
            self._inputpos[w.name] = ipos, self._limbs(w)
            self._inputbw[w.name] = w.bitwidth
            ipos +=self._limbs(w)
        self._ibufsz = ipos

        # 记录Memory写
        self.memory_update = []
        # 记录每个CondPart的输入信号，可能是外部输入信号，也可能是内部信号
        self.condPart_inputs = []

        # 生成for循环
        write('  ' + 'uint64_t input_pos = 0,output_pos = 0;')
        write('  long count=0;')
        write('  ' + 'for (uint64_t stepnum = 0; stepnum < stepcount; stepnum++)')
        write('  ' + '{')

        # 给每拍输入赋值
        for w in self.input_order:
            for n in range(self._limbs(w)):
                write('  ' * 2 + '{vn}[{n}]=*(inputs+input_pos);'.format(vn=self.varname[w], n=n))
                write('  ' * 2 + 'input_pos += 1;')
        write('')

        # 为每个分区生成模拟函数
        for cp in stmtsOrdered:
            id=cp.id
            if isinstance(cp,CondPart):
                #确定每个分区对外部输入信号的依赖,并更新Flag
                CondPart_external_input=[]
                for i in self.block.wirevector_set:
                    if not isinstance(i, Const):
                        if (i.name in cp.inputs) and not isinstance(i, Register) and i.name not in self.outputsToDeclare:
                            CondPart_external_input.append(i)
                if self.optimization in [2, 3]:
                    for input_ in CondPart_external_input:
                        for n in range(self._limbs(input_)):
                            write('  ' * 2 + 'Flag_same_inputs[{num}] &= {vn}[{n}]=={vn}$old[{n}];'.format(num=id,vn=self.varname[input_],n=n))

                    #根据Flag结果决定是否执行
                    write('  ' * 2+'if (Flag_same_inputs[{num}]==0)'.format(num=id))
                    write('  ' * 2+'{')
                    if not cp.alwaysActive:
                        write('  ' * 3 + 'Flag_same_inputs[{num}]=1;'.format(num=id))
                        write('')

                    memberStmts = cp.memberStmts
                    keepAvail = []
                    for i in cp.outputsToDeclare:
                        keepAvail.append(i)
                    self.writeBodyInner(StatementGraph(memberStmts), True, write, 3, id, keepAvail)
                    write('')
                else:
                    memberStmts = cp.memberStmts
                    keepAvail = []
                    for i in cp.outputsToDeclare:
                        keepAvail.append(i)
                    self.writeBodyInner(StatementGraph(memberStmts), True, write, 2, id, keepAvail)
                    write('')

                if self.optimization in [2, 3]:
                    for out in cp.outputsToDeclare:
                        out_information=self.outputsToDeclare_information[out]
                        vn=self.varname[out_information]
                        for dependence_id in self.outputsToDeclare_dependence[out]:
                            for n in range(self._limbs(out_information)):
                                write(2*'  '+'Flag_same_inputs[{id}] &= {name}[{n}]=={name}$old[{n}];'.format(id=dependence_id,n=n, name=vn))
                        for n in range(self._limbs(out_information)):
                            write(2*'  '+'{name}$old[{n}]={name}[{n}];'.format(n=n, name=vn))
                    write('  ' * 2 + '}')
                    write('')

        if self.optimization in [2, 3]:
        # 每一拍更新register，并修改使用者的Flag
            for register in self.block.wirevector_subset(Register):
                if register.name in self.register_dependence:
                    for dependence_id in self.register_dependence[register.name]:
                        for n in range(self._limbs(register)):
                            write(2 * '  ' + 'Flag_same_inputs[{id}] &= {vn}$new[{n}]=={vn}[{n}];'.format(
                                vn=self.varname[register], n=n, id=dependence_id))
                for n in range(self._limbs(register)):
                    write(2 * '  ' + '{vn}[{n}] = {vn}$new[{n}];'.format(vn=self.varname[register], n=n))
            write('')

        # 每一拍更新内存写入，并修改使用者的Flag
            for memory_update in self.memory_update:
                op, param, args, dest = memory_update.op, memory_update.op_param, memory_update.args, memory_update.dests
                self._build_memory_write(write, op, param, args, dest, 2)
                memory = param[1].name
                if memory in self.memory_dependence:
                    for dependence_id in self.memory_dependence[memory]:
                        write(2 * '  ' + 'Flag_same_inputs[{id}] = 0;'.format(id=dependence_id))
            write('')

        # output copied out
        write('\n//output\n')
        outputs = list(self.block.wirevector_subset(Output))
        self._outputpos = {}
        opos = 0
        for w in outputs:
            self._outputpos[w.name] = opos, self._limbs(w)
            for n in range(self._limbs(w)):
                write('  outputs[output_pos] = {vn}[{n}];'.format(vn=self.varname[w], n=n))
                write('  output_pos += 1;')
                opos += 1
        self._obufsz = opos

        if self.optimization in [2, 3]:
            for input in self.block.wirevector_subset(Input):
                for n in range(self._limbs(input)):
                    write('  ' * 2 + '{vn}$old[{n}]={vn}[{n}];'.format(n=n, vn=self.varname[input]))
        write('  ' + '}')
        write('}')

